/**
 * 
 */
package idv.takeshi.software.productline.bookstore.infrastructure.persistence.jpa;

import java.util.LinkedHashSet;
import java.util.Set;

import javax.persistence.EntityExistsException;

import javax.persistence.EntityManager;
import javax.persistence.NoResultException;
import javax.persistence.PersistenceContext;
import javax.persistence.Query;

import org.apache.commons.lang.Validate;
import org.springframework.stereotype.Repository;

import idv.takeshi.software.productline.bookstore.domain.model.book.BookRepository;
import idv.takeshi.software.productline.bookstore.domain.model.book.Book;
import idv.takeshi.software.productline.bookstore.domain.model.book.BookDuplicateException;
import idv.takeshi.software.productline.bookstore.domain.model.book.Inventory;
import idv.takeshi.software.productline.bookstore.domain.model.book.Promotion;

/**
 * A model repository implementation for BookRepository.
 * @author takeshi
 *
 */
@Repository("bookRepository")
public class BookRepositoryImpl implements BookRepository {
	
	@PersistenceContext
	private EntityManager entityManager;
	
	/* (non-Javadoc)
	 * @see idv.takeshi.software.productline.bookstore.domain.model.book.BookRepository#add(idv.takeshi.software.productline.bookstore.domain.model.book.Book)
	 */
	public void add(Book book) throws BookDuplicateException {
		Validate.notNull(book, "book shall always not be null");
		try {
			this.entityManager.persist(book);
		} catch(EntityExistsException  e){
			throw new BookDuplicateException("book:" + book.getName() + " is duplicate");
		}
	}

	/* (non-Javadoc)
	 * @see idv.takeshi.software.productline.bookstore.domain.model.book.BookRepository#findById(java.lang.Long)
	 */
	public Book findById(Long id) {
		Validate.notNull(id, "id shall always not be null");
		Book book = this.entityManager.find(Book.class, id);
		return book;
	}

	/* (non-Javadoc)
	 * @see idv.takeshi.software.productline.bookstore.domain.model.book.BookRepository#findByName(java.lang.String)
	 */
	public Book findByName(String name) {
		Validate.notEmpty(name, "name shall always not be null/empty");
		Book book = null;
		Query query = this.entityManager.createNamedQuery("findByName");
		query.setParameter("name", name);
		try {
			book = (Book) query.getSingleResult();
		} catch(NoResultException e){
			//DO NOTHING
		}
		return book;
	}

	/* (non-Javadoc)
	 * @see idv.takeshi.software.productline.bookstore.domain.model.book.BookRepository#remove(idv.takeshi.software.productline.bookstore.domain.model.book.Book)
	 */
	public void remove(Book book) {
		Validate.notNull(book, "book shall always not be null");
		this.entityManager.remove(book);
	}

	public void add(Inventory inventory) {
		Validate.notNull(inventory, "inventory shall always not be null");
		this.entityManager.persist(inventory);
	}

	public void add(Promotion promotion) {
		Validate.notNull(promotion, "promotion shall always not be null");
		this.entityManager.persist(promotion);
	}

	public void remove(Inventory inventory) {
		Validate.notNull(inventory, "inventory shall always not be null");
		this.entityManager.remove(inventory);
	}

	public void remove(Promotion promotion) {
		Validate.notNull(promotion, "promotion shall always not be null");
		this.entityManager.remove(promotion);
	}
	
	/**
	 * update a book.
	 * @param book
	 */
	public void update(Book book){
		Validate.notNull(book, "book shall always not be null");
		Inventory inventory = null;
		Promotion promotion = null;
		
		this.entityManager.merge(book);
		
		inventory = book.getInventory();
		if(null != inventory)
			this.entityManager.merge(inventory);
		
		promotion = book.getPromotion();
		if(null != promotion)
			this.entityManager.merge(promotion);
	}

	/* (non-Javadoc)
	 * @see idv.takeshi.software.productline.bookstore.domain.model.book.BookRepository#findBooksByKeyWord(java.lang.String, int, int)
	 */
	@SuppressWarnings("unchecked")
	public Set<Book> findBooksByKeyWord(String keyWord, int fromTopN, int toTopN) {
		Validate.notEmpty(keyWord, "keyWord shall always not be null/empty");
		Validate.isTrue(fromTopN > 0, "fromTopN:" + fromTopN + " shall greater than 0");
		Validate.isTrue(fromTopN < toTopN, "toTopN:" + toTopN + " shall greater than fromTopN:" + 
				fromTopN);
		
		int tmpFromTopN = fromTopN - 1;
		int tmpToTopN = toTopN - tmpFromTopN;
		
		Query query = this.entityManager.createNamedQuery("findByKeyWord");
		query.setParameter("keyWord", "%" + keyWord + "%");
		query.setFirstResult(tmpFromTopN);
		query.setMaxResults(tmpToTopN);
		return new LinkedHashSet<Book>(query.getResultList());
	}

	/* (non-Javadoc)
	 * @see idv.takeshi.software.productline.bookstore.domain.model.book.BookRepository#findBooksByKeyWord(java.lang.String, int)
	 */
	public Set<Book> findBooksByKeyWord(String keyWord, int topN) {
		return findBooksByKeyWord(keyWord, 1, topN);
	}

	/* (non-Javadoc)
	 * @see idv.takeshi.software.productline.bookstore.domain.model.book.BookRepository#findBooksByKeyWord(java.lang.String)
	 */
	@SuppressWarnings("unchecked")
	public Set<Book> findBooksByKeyWord(String keyWord) {
		Validate.notEmpty(keyWord, "keyWord shall always not be null/empty");
		
		Query query = this.entityManager.createNamedQuery("findByKeyWord");
		query.setParameter("keyWord", "%" + keyWord + "%");
		return new LinkedHashSet<Book>(query.getResultList());
	}

	/* (non-Javadoc)
	 * @see idv.takeshi.software.productline.bookstore.domain.model.book.BookRepository#findBooksAmountByKeyWord(java.lang.String)
	 */
	public int findBooksAmountByKeyWord(String keyWord) {
		Validate.notEmpty(keyWord, "keyWord shall always not be null/empty");
		Long amount = null;
		Query query = this.entityManager.createNamedQuery("findAmountByKeyWord");
		query.setParameter("keyWord", "%" + keyWord + "%");
		amount = (Long) query.getSingleResult();
		return amount.intValue();
	}

}
